package com.hibase.core.rule.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.hibase.common.exception.HibaseException;
import com.hibase.core.rule.config.RuleMethodProcessor;
import com.hibase.core.rule.entity.RuleMethod;
import com.hibase.core.rule.entity.ruledebug.HibaseRuleDebugLog;
import com.hibase.core.rule.entity.ruledebug.RuleDebugResultVO;
import com.hibase.core.rule.entity.rulemenu.RuleMenuContent;
import com.hibase.core.rule.service.ruleexecute.RuleExecuteService;
import com.hibase.core.rule.utils.operator.*;
import com.ql.util.express.DynamicParamsUtil;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.IExpressContext;
import com.ql.util.express.config.QLExpressRunStrategy;
import com.ql.util.express.exception.QLBizException;
import com.ql.util.express.exception.QLException;
import com.ql.util.express.instruction.op.*;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 描述:
 *
 * @author li-wei-hua
 * @date 2020-03-20 14:31
 */
@Service
public class HibaseRuleEngineUtils implements ApplicationContextAware {

    private static final HibaseRuleDebugLog log = new HibaseRuleDebugLog(LoggerFactory.getLogger(HibaseRuleEngineUtils.class));

    private ApplicationContext applicationContext;

    private static ExpressRunner runner;

    public static ThreadLocal<HibaseRuleDebugLog> localLog = new ThreadLocal<HibaseRuleDebugLog>();

    @Override
    public void setApplicationContext(ApplicationContext context) {

        this.applicationContext = context;
        runner = initRunner();
    }


    public Object executeExpress(RuleMenuContent content, Map<String,Object> context)  throws Exception {
        List<String> errorList = new ArrayList<>();
        // 开启逻辑短路
        HibaseRuleDebugLog ruleLog = new HibaseRuleDebugLog(LoggerFactory.getLogger(HibaseRuleEngineUtils.class));
        localLog.set(ruleLog);
        runner.setShortCircuit(true);
        IExpressContext<String,Object> expressContext = new SpringBeanContext(context,this.applicationContext);

        try{
            return runner.execute(content.getContent(), expressContext, errorList, true, false, ruleLog);
        }
        catch(HibaseException e){
            ruleLog.error("HibaseException运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【HibaseException规则错误】 请检查规则语法 :" .concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(QLException e){
            ruleLog.error("QLException运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【QLException规则错误】 请检查规则语法 :" .concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(QLBizException e){
            ruleLog.error("QLBizException运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【QLBizException规则错误】 请检查规则语法 :" .concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(Exception e){
            ruleLog.error("Exception运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【Exception规则错误】 请检查规则语法 :".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
    }

    public Object executeChildExpress(RuleMenuContent content, Map<String,Object> context) throws Exception {
        ExpressRunner childrunner = initRunner();
        // 开启逻辑短路
        //HibaseRuleDebugLog childLog = new HibaseRuleDebugLog(LoggerFactory.getLogger(HibaseRuleEngineUtils.class));
        HibaseRuleDebugLog childLog = localLog.get();
        childrunner.setShortCircuit(true);
        childLog.debug("开始执行:".concat(content.getName()));
        IExpressContext<String,Object> expressContext = new SpringBeanContext(context,this.applicationContext);
        List<String> errorList = new ArrayList<>();
        try{
            return childrunner.execute(content.getContent(), expressContext, errorList, true, true, childLog);
        }
        catch(HibaseException e){
            childLog.error("HibaseException运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【HibaseException规则错误】 请检查规则语法 :" .concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(QLException e){
            childLog.error("QLException运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【QLException规则错误】 请检查规则语法 :" .concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(QLBizException e){
            childLog.error("QLBizException运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【QLBizException规则错误】 请检查规则语法 :" .concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(Exception e){
            childLog.error("Exception运行出错！".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            throw new HibaseException("【Exception规则错误】 请检查规则语法 :".concat(content.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }

    }

    /**
     * 按调试模式执行规则
     * @return
     */
    public RuleDebugResultVO executeRuleByDebug(RuleMenuContent ruleMenuContent, Map<String,Object> context) {
        HibaseRuleDebugLog debugLog = new HibaseRuleDebugLog(LoggerFactory.getLogger(HibaseRuleEngineUtils.class));
        localLog.set(debugLog);
        //关闭逻辑短路
        runner.setShortCircuit(true);
        debugLog.debug("开始执行:".concat(ruleMenuContent.getName()));
        Object result = null;
        List<String> errorList = new ArrayList<>();
        IExpressContext<String,Object> expressContext = new SpringBeanContext(context,this.applicationContext);
        try{
            result = runner.execute(ruleMenuContent.getContent(), expressContext, errorList, true, true, debugLog);
        }
        catch(HibaseException e){
            debugLog.error("HibaseException运行出错！".concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            //throw new HibaseException("【HibaseException规则错误】 请检查规则语法 :" .concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(QLException e){
            debugLog.error("QLException运行出错！".concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            //throw new HibaseException("【QLException规则错误】 请检查规则语法 :" .concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(QLBizException e){
            debugLog.error("QLBizException运行出错！".concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            //throw new HibaseException("【QLBizException规则错误】 请检查规则语法 :" .concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        catch(Exception e){
            debugLog.error("Exception运行出错！".concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())),e);
            //throw new HibaseException("【Exception规则错误】 请检查规则语法 :".concat(ruleMenuContent.getName()).concat(ExceptionUtil.getRootCauseMessage(e.getCause())));
        }
        List<String> debugLogList = new ArrayList<>(localLog.get().getDebugList());
        debugLogList.addAll(debugLog.getErrorList());
        debugLog.clear();
        // 组装 结果和日志
        RuleDebugResultVO ruleDebugResultVO = new RuleDebugResultVO();

        ruleDebugResultVO.setDebugLog(debugLogList);
        ruleDebugResultVO.setResult(result);
        return ruleDebugResultVO;
    }



    private ExpressRunner initRunner() {
        ExpressRunner instance = null;
        // 禁止调用不安全的方法

        try {
            QLExpressRunStrategy.setForbiddenInvokeSecurityRiskMethods(Boolean.TRUE);
            instance = new ExpressRunner(true, true);

            //采用不定参数
            DynamicParamsUtil.supportDynamicParams = true;
            instance.addOperatorWithAlias("取模","mod", null);
            instance.addOperatorWithAlias("包含","in", null);
            instance.addOperatorWithAlias("遍历","for", null);
            instance.addOperatorWithAlias("如果","if", null);
            instance.addOperatorWithAlias("当","when", null);
            instance.addOperatorWithAlias("则","then", null);
            instance.addOperatorWithAlias("否则","else", null);
            instance.addOperatorWithAlias("创建全局别名","exportAlias", null);
            instance.addOperatorWithAlias("创建别名","alias", null);
            instance.addOperatorWithAlias("结束遍历","break", null);
            instance.addOperatorWithAlias("跳出当前遍历","continue", null);
            instance.addOperatorWithAlias("返回","return", null);
            instance.addOperatorWithAlias("定义宏","macro", null);
            instance.addOperatorWithAlias("定义方法","function", null);
            instance.addOperatorWithAlias("创建变量","def", null);
            instance.addOperatorWithAlias("创建全局变量","exportDef", null);
            instance.addOperatorWithAlias("创建","new", null);
            instance.addOperatorWithAlias("数组","array", null);
            instance.addOperatorWithAlias("匿名数组","anonymousNewArray", null);
            instance.addOperatorWithAlias("模糊","like", null);
            instance.addOperatorWithAlias("打印","println", null);
            instance.addOperatorWithAlias("小于","<","$1 小于 $2");
            instance.addOperatorWithAlias("小于等于","<=","$1 小于等于 $2");
            instance.addOperatorWithAlias("大于",">","$1 小于 $2");
            instance.addOperatorWithAlias("大于等于",">=","$1 大于等于 $2");
            instance.addOperatorWithAlias("不等于","!=","$1 不等于 $2");

            instance.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs",
                    new String[] { "double" }, null);
            instance.addFunctionOfClassMethod("转换为大写", RuleUtils.class.getName(),
                    "upperCase", new String[] { "String" }, null);
            instance.addFunctionOfClassMethod("转换为小写", RuleUtils.class.getName(),
                    "lowerCase", new String[] { "String" }, null);
            instance.addFunction("创建对象", new OperatorAnonymousNewMap("创建对象"));
            //instance.addFunction("创建数组", new OperatorAnonymousNewList("创建数组"));
            instance.addFunction("创建数组", new OperatorAnonymousNewArray("创建数组"));
            instance.addFunction("创建列表", new OperatorAnonymousNewList("创建列表"));

            instance.addOperator("取最小值", new OperatorMinMax("min"));
            instance.addOperator("取最大值", new OperatorMinMax("max"));
            instance.addOperator("或者", new OperatorOr("或者"));

            instance.addOperator("join",new JoinOperator());
            instance.addOperatorWithAlias("且","&&","$1 且 $2");
            instance.addOperatorWithAlias("或","或者","$1 或 $2");

            instance.addOperatorWithAlias("添加","join",null);
            // 注入spring管理的bean和方法
            // 直接查询SQL
            instance.addFunctionOfServiceMethod("查询数据",applicationContext.getBean(RuleExecuteService.class),"executeSelectSql",new Class[]{String.class,String.class,Object[].class},null);
            // 查询SQL并拼接{0}，{1}的参数，,自动解析Map匹配只传一个参数
            instance.addFunctionOfServiceMethod("查询数据集",applicationContext.getBean(RuleExecuteService.class),"getDataSet",new Class[]{String.class,Object[].class},null);
            //
            instance.addClassMethod("长度",List.class,new SizeOperator());
            instance.addClassMethod("新增",List.class,new AddOperator());

            instance.addClassMethod("得到",List.class,new GetOperator());

            instance.addClassMethod("移除",List.class,new RemoveOperator());

            instance.addClassField("长度",Object.class,new LengthOperator());

            //得到当前时间戳
            instance.addFunctionOfClassMethod("得到当前时间戳", DateUtil.class.getName(),"current", new String[] { "boolean" },null);
            //字符串转时间戳
            instance.addFunctionOfClassMethod("字符串转时间戳", DateUtils.class.getName(),"timestamp", new Class[] { String.class,String.class },null);
            //时间戳比较大小
            instance.addFunctionOfClassMethod("时间戳比较", DateUtils.class.getName(),"compare", new String[] { "long","long" },null);
            // 查询动态表
            //instance.addFunctionOfServiceMethod("查询动态表",applicationContext.getBean(RuleExecuteService.class),"getRuleData2",new Class[]{String.class,Object[].class},null);
            //调用子规则
            instance.addFunctionOfServiceMethod("调用子规则",applicationContext.getBean(RuleExecuteService.class),"callChildRule",new Class[]{String.class,Map.class},null);

            //时间戳转字符串
            instance.addFunctionOfClassMethod("时间戳转字符串", DateUtils.class.getName(),"timestamp", new Class[] { Long.class,String.class },null);

            //增加天数增加时间
            instance.addFunctionOfClassMethod("增加天数增加时间", DateUtils.class.getName(),"getTimestampByAddTime", new Class[] { Object.class,String.class,String.class },null);

            instance.addFunctionOfClassMethod("字符串转字典", RuleUtils.class.getName(),"stringToMap", new Class[] { String.class },null);

            instance.addFunctionOfClassMethod("类型", RuleUtils.class.getName(),"getType", new Class[] { Object.class },null);

            instance.addFunctionOfServiceMethod("查询动态表单一结果",applicationContext.getBean(RuleExecuteService.class),"getOneRuleData",new Class[]{String.class,Object.class,Object[].class},null);

            //instance.addFunctionOfServiceMethod("查询动态表优先级",applicationContext.getBean(RuleExecuteService.class),"getPriorityRuleData",new Class[]{String.class,Object[].class},null);

            instance.addFunctionOfServiceMethod("查询动态表",applicationContext.getBean(RuleExecuteService.class),"getRuleData3",new Class[]{String.class,Object.class,Object[].class},null);

            instance.addFunctionOfClassMethod("字符型转整型", RuleUtils.class.getName(),"stringToInteger", new Class[] { String.class },null);

            instance.addFunctionOfClassMethod("相乘", RuleUtils.class.getName(),"multiply", new Class[] { String.class,String.class },null);

            instance.addFunctionOfClassMethod("字符串转日期", DateUtils.class.getName(),"stringToDate", new Class[] { String.class},null);

            instance.addFunctionOfClassMethod("日期格式化", DateUtils.class.getName(),"format", new Class[] { Object.class,String.class},null);

            instance.addFunctionOfClassMethod("校验正则", RuleUtils.class.getName(),"regExp", new Class[] { String.class,String.class},null);

            instance.addFunctionOfClassMethod("格式化字符", StrUtil.class.getName(),"padPre", new Class[] { CharSequence.class,int.class,CharSequence.class},null);

            instance.addFunctionOfClassMethod("字符串截取", StrUtil.class.getName(),"sub", new Class[] { CharSequence.class,int.class,int.class},null);

            instance.addFunctionOfClassMethod("字符串切割", StrUtil.class.getName(),"split", new Class[] { CharSequence.class,char.class},null);

            instance.addFunctionOfClassMethod("字符串拼接", StrUtil.class.getName(),"concat", new Class[] { boolean.class,CharSequence[].class},null);

            for(RuleMethod ruleMethod : RuleMethodProcessor.RULE_METHODS){
                instance.addFunctionOfClassMethod(ruleMethod.getAlias(),
                        ruleMethod.getBean().getClass().getName(),
                        ruleMethod.getMethod().getName(),
                        ruleMethod.getMethod().getParameterTypes(),
                        null
                        );
            }
        }catch (Exception e){
            log.error("初始化 规则引擎失败");
        }
        return instance;
    }

    public static void main(String[] args) {

    }

    public boolean checkSyntax(String text){
        return runner.checkSyntax(text);
    }
}